from functools import wraps
from typing import Optional, Any, Callable
import redis
import json
from datetime import timedelta
from app import logger
from flask import current_app

class RedisCache:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self):
        try:
            config = current_app.config['REDIS']
            self.redis = redis.Redis(
                host=config['host'],
                port=config['port'],
                db=config['db'],
                password=config['password'],
                decode_responses=config['decode_responses']
            )
        except KeyError as e:
            raise RuntimeError(f"Redis configuration missing: {str(e)}")
        except redis.ConnectionError as e:
            raise RuntimeError(f"Could not connect to Redis: {str(e)}")
    
    def get(self, key: str) -> Optional[str]:
        try:
            return self.redis.get(key)
        except Exception as e:
            logger.error(f"Redis get error: {str(e)}")
            return None
    
    def set(
        self,
        key: str,
        value: str,
        expire: Optional[int] = None
    ) -> bool:
        try:
            return self.redis.set(key, value, ex=expire)
        except Exception as e:
            logger.error(f"Redis set error: {str(e)}")
            return False
    
    def delete(self, key: str) -> bool:
        try:
            return bool(self.redis.delete(key))
        except Exception as e:
            logger.error(f"Redis delete error: {str(e)}")
            return False
    
    def exists(self, key: str) -> bool:
        try:
            return bool(self.redis.exists(key))
        except Exception as e:
            logger.error(f"Redis exists error: {str(e)}")
            return False
    
    def incr(self, key: str) -> int:
        try:
            return self.redis.incr(key)
        except Exception as e:
            logger.error(f"Redis incr error: {str(e)}")
            return 0
    
    def expire(self, key: str, seconds: int) -> bool:
        try:
            return bool(self.redis.expire(key, seconds))
        except Exception as e:
            logger.error(f"Redis expire error: {str(e)}")
            return False

def cache(
    expire: int = 300,
    key_prefix: str = '',
    unless: Optional[Callable] = None
):
    """缓存装饰器"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            if unless and unless():
                return f(*args, **kwargs)
            
            # 生成缓存键
            cache_key = f"{key_prefix}:{f.__name__}:{hash(str(args) + str(kwargs))}"
            
            # 获取缓存
            cache_client = RedisCache()
            result = cache_client.get(cache_key)
            
            if result is not None:
                return json.loads(result)
            
            # 执行函数
            result = f(*args, **kwargs)
            
            # 设置缓存
            try:
                cache_client.set(
                    cache_key,
                    json.dumps(result),
                    expire
                )
            except (TypeError, ValueError) as e:
                logger.warning(f"Cache serialization failed: {str(e)}")
            
            return result
        return decorated_function
    return decorator 